/**
 * Created by IntelliJ IDEA.
 * User: martlenn
 * Date: 03-Dec-2006
 * Time: 12:14:50
 */
package edu.jhmi.tools.omssa_gui.omssa_readers;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
/*
 * CVS information:
 *
 * $Revision$
 * $Date$
 */

/**
 * This class reads the OMSSA configuration file to obtain all known
 * modifications.
 *
 * @author Lennart Martens
 * @version $Id$
 */
public class OMSSAModificationReader {

    /**
     * The XmlPullParser that reads the OMSSA files.
     */
    private XmlPullParser parser = null;

    /**
     * The ArrayList with the parsed modifications. Consists of 'OMSSA_GUI_Modification'
     * instances. 
     */
    private ArrayList modifications = new ArrayList();

    /**
     * This HashMap has all the modifications from the ArrayList, but keyed by
     * their name for easy look-up.
     */
    private HashMap modHash = new HashMap();

    /**
     * This constructor initializes a list of modifications based on the information
     * specified in the OMSSA installation folder (currently: 'mods.xml' and 'usermods.xml').
     *
     * @param aOMSSAInstallFolder   File with the OMSSA installation folder.
     * @throws XmlPullParserException   when the pull parser failed.
     * @throws IOException  when the file(s) could not be read.
     */
    public OMSSAModificationReader(File aOMSSAInstallFolder) throws XmlPullParserException, IOException {
        // Sanity check: is the File a folder?
        if(!aOMSSAInstallFolder.isDirectory()) {
            throw new IllegalArgumentException("The OMSSA installation folder you specified ('" + aOMSSAInstallFolder.getAbsoluteFile() + "') is not a directory!");
        }
        // Create the pull parser.
        XmlPullParserFactory factory = XmlPullParserFactory.newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null);
        factory.setNamespaceAware(true);
        parser = factory.newPullParser();
        // Now that we have our pull parser, get the first file (default one).
        File inputFile = new File(aOMSSAInstallFolder, "mods.xml");
        // Parse it.
        parseModFile(inputFile, false, false);
        // Search for second file - optional.
        inputFile = new File(aOMSSAInstallFolder, "usermods.xml");
        if(inputFile.exists()) {
            // File exists, parse it.
            parseModFile(inputFile, true, true);
        }
    }

    /**
     * Original, hardcoded values.
     * 
     * @return
     */
    public static String[] getModifications(String aInputFile) {
        // Still hardcoded right now.
        return new String[] {
            "methylation of K",
            "oxidation of M",
            "carboxymethyl C",
            "carbamidomethyl C",
            "deamidation of N and Q",
            "propionamide C",
            "phosphorylation of S",
            "phosphorylation of T",
            "phosphorylation of Y",
            "M cleavage from protein n-term",
            "acetylation of protein n-term",
            "methylation of protein n-term",
            "tri-methylation of protein n-term",
            "beta methythiolation of D",
            "methylation of Q",
            "tri-methylation of K",
            "methylation of D",
            "methylation of E",
            "methylation of peptide c-term",
            "tri-deuteromethylation of D",
            "tri-deuteromethylation of E",
            "tri-deuteromethylation of peptide c-term",
            "n-formyl met addition",
            "2-amino-3-oxo-butanoic acid T",
            "acetylation of K",
            "amidation of peptide c-term",
            "beta-methylthiolation of D",
            "carboxyamidomethylation of K",
            "carboxyamidomethylation of H",
            "carboxyamidomethylation of D",
            "carboxyamidomethylation of E",
            "carbamylation of K",
            "carbamylation of n-term peptide",
            "citrullination of R",
            "oxidation of C to cysteic acid",
            "di-iodination of Y",
            "di-methylation of K",
            "di-methylation of R",
            "di-methylation of peptide n-term",
            "oxidation of F to dihydroxyphenylalanine",
            "thioacylation of K",
            "thioacylation of peptide n-term",
            "farnesylation of C",
            "formylation of K",
            "formylation of peptide n-term",
            "oxidation of W to formylkynurenin",
            "fluorophenylalanine",
            "gamma-carboxylation of D",
            "gamma-carboxylation of E",
            "geranyl-geranyl",
            "glucuronylation of protein n-term",
            "glutathione disulfide",
            "ubiquitinylation residue",
            "guanidination of K",
            "oxidation of H to N",
            "oxidation of H to D",
            "homoserine",
            "homoserine lactone",
            "oxidation of W to hydroxykynurenin",
            "hydroxylation of D",
            "hydroxylation of K",
            "hydroxylation of N",
            "hydroxylation of P",
            "hydroxylation of F",
            "hydroxylation of  Y",
            "iodination of Y",
            "oxidation of W to kynurenin",
            "lipoyl K",
            "methyl ester of peptide c-term",
            "methyl ester of D",
            "methyl ester of E",
            "methyl ester of S",
            "methyl ester of Y",
            "methyl C",
            "methyl H",
            "methyl N",
            "methylation of peptide n-term",
            "methyl R",
            "myristoleylation of G",
            "myristoyl-4H of G",
            "myristoylation of peptide n-term G",
            "myristoylation of K",
            "formylation of protein n-term",
            "NEM C",
            "NIPCAM",
            "oxidation of W to nitro",
            "oxidation of Y to nitro",
            "O18 on peptide n-term",
            "di-O18 on peptide n-term",
            "oxidation of H",
            "oxidation of W",
            "phosphopantetheine S",
            "palmitoylation of C",
            "palmitoylation of K",
            "palmitoylation of S",
            "palmitoylation of T",
            "phosphorylation of S with prompt loss",
            "phosphorylation of T with prompt loss",
            "phosphorylation with prompt loss on Y",
            "phosphorylation of C",
            "phosphorylation of D",
            "phosphorylation of H",
            "propionyl light K",
            "propionyl light on peptide n-term",
            "propionyl heavy K",
            "propionyl heavy peptide n-term",
            "pyridyl K",
            "pyridyl peptide n-term",
            "pyro-cmC",
            "pyro-glu from n-term E",
            "pyro-glu from n-term Q",
            "oxidation of P to pyroglutamic acid",
            "s-pyridylethylation of C",
            "SeMet",
            "sulfation of Y",
            "sulphone of M",
            "tri-iodination of Y",
            "tri-methylation of R",
            "n-acyl diglyceride cysteine",
            "ICAT light",
            "ICAT heavy",
            "CAMthiopropanoyl K",
            "phosphorylation of S with neutral loss",
            "phosphorylation of T with neutral loss",
            "phosphorylation of S with ETD loss",
            "phosphorylation of T with ETD loss"};
    }

    /**
     * This method returns the parsed set of modifications as an array of
     * OMSSA_GUI_Modification instances.
     *
     * @return  OMSSA_GUI_Modification[] with the modifications.
     */
    public OMSSA_GUI_Modification[] getModifications() {
        OMSSA_GUI_Modification[] result = new OMSSA_GUI_Modification[modifications.size()];
        modifications.toArray(result);
        return result;
    }

    /**
     * This method returns the OMSSA_GUI_Modification instance with the specified
     * name and number, or 'null' if none can be found.
     *
     * @param aName String with the modification name.
     * @param aNumber   int with the modification number.
     * @return  OMSSA_GUI_Modification instance if it is found, 'null' otherwise.
     */
    public OMSSA_GUI_Modification getModificationByName(String aName, int aNumber) {
        return (OMSSA_GUI_Modification)modHash.get(aNumber + ". " + aName);
    }

    /**
     * This method parses an entire OMSSA modification file. It will either append to or
     * overwrite the collection of modifications currently known on this object. It will
     * also annotate the modifications as user-derived or OMSSA-predefined.
     *
     * @param aFile File to read the modifications from.
     * @param aAppendToExistingList boolean to choose appending ('true') to the existing list
     *                              of modifications, or overwriting ('false') of this list.
     * @param aUserMods boolean to indicate whether the parsed modifications should be flagged
     *                  as user-specified modifications, or OMSSA-predefined modifications.
     * @throws XmlPullParserException   when the pull parser failed.
     * @throws IOException  when the file could not be read.
     */
    private void parseModFile(File aFile, boolean aAppendToExistingList, boolean aUserMods) throws XmlPullParserException, IOException {
        // See if we have to clear the current modifications.
        if(!aAppendToExistingList) {
            modifications = new ArrayList();
            modHash = new HashMap();
        }
        // Create a reader for the input file.
        BufferedReader br = new BufferedReader(new FileReader(aFile));
        // Set the XML Pull Parser to read from this reader.
        parser.setInput(br);
        // Start the parsing.
        int type = parser.next();
        // Go through the whole document.
        while(type != XmlPullParser.END_DOCUMENT) {
            // If we find a 'MSModSpec' start tag,
            // we should parse the mod.
            if(type == XmlPullParser.START_TAG && parser.getName().equals("MSModSpec")) {
                OMSSA_GUI_Modification modification = parseMSModSpec(parser);
                modification.setUserModification(aUserMods);
                modifications.add(modification);
                modHash.put(modification.getNumber() + ". " + modification.getName(), modification);
            }
            type = parser.next();
        }
        br.close();
    }

    /**
     * This method parses a single MSModSpec tag's contents from the OMSSA modifications XML file.
     * It expects the XmlPullParser to be set on the starting 'MSModSpec' tag, and upon completion,
     * the parser will be set on the closing 'MSModSpec' tag.
     *
     * @param aParser   XmlPullParser to parse the tag's content from. Should be set on
     *                                the starting 'MSModSpec' tag
     * @return  OMSSA_GUI_Modification  with the parsed modification.
     * @throws XmlPullParserException   when the pull parser failed.
     * @throws IOException  when the pull parser could not access the underlying file.
     */
    private OMSSA_GUI_Modification parseMSModSpec(XmlPullParser aParser) throws XmlPullParserException, IOException {
        // Check whether the XmlPullParser is correctly positioned (i.e. directly on the 'MSModSpec' start tag)
        if(!(aParser.getName().equals("MSModSpec") && aParser.getEventType() == XmlPullParser.START_TAG)) {
            throw new IllegalArgumentException("XmlPullParser should have been on the start tag for 'MSModSpec', but was on '" + aParser.getName() +  "' instead!");
        }
        // Structure now is: tag, whitespace, tag, NUMBER (which we need).
        // Start tag.
        aParser.nextTag();
        // Validate correctness.
        if(!aParser.getName().equals("MSModSpec_mod")) {
            throw new XmlPullParserException("Found tag '" + parser.getName() + "' where 'MSModSpec_mod' was expected on line " + parser.getLineNumber() + "!");
        }
        aParser.nextTag();
        // Validate correctness.
        if(!aParser.getName().equals("MSMod")) {
            throw new XmlPullParserException("Found tag '" + parser.getName() + "' where 'MSMod' was expected on line " + parser.getLineNumber() + "!");
        }
        // We need the value here.
        aParser.next();
        String numberString = aParser.getText();
        int number = -1;
        try {
            number = Integer.parseInt(numberString);
        } catch(NumberFormatException nfe) {
            throw new XmlPullParserException("Found non-parseable text '" + numberString + "' for the value of the 'MSMod' tag on line " + parser.getLineNumber() + "!");
        }
        // OK, we got the number. Progress to the user-readable name.
        int type = aParser.next();
        while(!(type == XmlPullParser.START_TAG && aParser.getName().equals("MSModSpec_name"))) {
            type = aParser.next();
        }
        // Right, we should be on the right start tag, so get the value.
        aParser.next();
        String name = aParser.getText().trim();
        // Move the parser to the end tag of this modification.
        type = aParser.next();
        while(!(type == XmlPullParser.END_TAG && aParser.getName().equals("MSModSpec"))) {
            type = aParser.next();
        }

        // Create and return modification.
        return new OMSSA_GUI_Modification(name, number);
    }
}
